/*
 * semaphoreTests.c
 *
 * Created: 2015-01-06 12:08:10
 *  Author: Andreas Domfors
 */ 

#include <stdbool.h>
#include <malloc.h>
#include "drivers/delay.h"

#include "API/rosa.h"
#include "ee_printf.h"
#include "kernel/rosa_queues.h"
#include "kernel/rosa_scheduler.h"
#include "kernel/rosa_ext.h"

#include "testFramework.h"

semaphore s, t;

void testSemaphoreCreation(void)
{
	int result = ROSA_createBinarySemaphore(&s);
	testIntEq(result, 0);
}

void testSemaphoreWaitTask(void)
{
	int result = ROSA_waitBinarySemaphore(&s, 1);
	testIntEq(result, 0);
	
	result = ROSA_waitBinarySemaphore(&s, 10);
	testIntEq(result, -2);
	
	result = ROSA_signalBinarySemaphore(&s);
	testIntEq(result, 0);
	
	result = ROSA_waitBinarySemaphore(&s, 1);
	testIntEq(result, 0);
	
	while(1);
}

void testSemaphoreWait(void)
{
	int result;
	//Create tasks and install them into the ROSA kernel
	result = ROSA_taskCreate("tst1", testSemaphoreWaitTask, 1, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	result = ROSA_taskCreate("stop", schedulerStopper, 10, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	
	result = ROSA_createBinarySemaphore(&s);
	testIntEq(result, 0);
	
	//Start the ROSA kernel
	ROSA_startScheduler();
}

void testSemaphoreOutOfMemoryCondition(void)
{
	int counter=0;
	while(1)
	{
		int result = ROSA_createBinarySemaphore(&s);
		counter++;
		switch(result)
		{
			case 0:
				continue;
			case -1:
				ee_printf("Out of memory. Created %d semaphores\n", counter);
				struct mallinfo m = mallinfo();
				ee_printf("Malloc memory used: %d free: %d bytes total: %d bytes\n", m.uordblks, m.fordblks, m.arena);
				return;
			default:
				ee_printf("Error in memory stress test: Unknown return code: %d\n", result);
				return;
		}
	}
}

static void semaphoreLockerOne(void)
{
	while(1)
	{
		ROSA_waitBinarySemaphore(&s, 0);
		ROSA_taskDelay(10);
		ROSA_waitBinarySemaphore(&t, 0);
		ROSA_taskDelay(10);
		ROSA_signalBinarySemaphore(&t);
		ROSA_signalBinarySemaphore(&s);
		ROSA_taskDelay(10);
	}
}

static void semaphoreLockerTwo(void)
{
	while(1)
	{
		ROSA_waitBinarySemaphore(&t, 0);
		ROSA_taskDelay(7);
		ROSA_waitBinarySemaphore(&s, 0);
		ROSA_taskDelay(7);
		ROSA_signalBinarySemaphore(&s);
		ROSA_signalBinarySemaphore(&t);
		ROSA_taskDelay(7);
	}
}

void testDeadlockCreation(void)
{
	int result;
	//Create tasks and install them into the ROSA kernel
	result = ROSA_taskCreate("tst1", semaphoreLockerOne, 1, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	result = ROSA_taskCreate("tst2", semaphoreLockerTwo, 1, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
		
	result = ROSA_taskCreate("stop", schedulerStopper, 10, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	
	result = ROSA_createBinarySemaphore(&s);
	testIntEq(result, 0);
	result = ROSA_createBinarySemaphore(&t);
	testIntEq(result, 0);
	
	//Start the ROSA kernel
	ROSA_startScheduler();
	
	testIntEq(rosa_blockedQueueLength(), 2);
}

void testIPCPsemaphoreCreation(void)
{
	int result = ROSA_createSemaphoreIPCP(&s, 10);
	testIntEq(result, 0);
}

#define SEM_CEILING 10
void testIPCPSemaphoreWaitTask(void)
{
	tcb *task = getCurrentTask();
	testIntEq(task->dynamicPriority, task->priority);
	int result = ROSA_waitSemaphoreIPCP(&s, 1);
	testIntEq(result, 0);
	testIntEq(getCurrentTask()->dynamicPriority, SEM_CEILING);
	
	result = ROSA_waitSemaphoreIPCP(&s, 10);
	testIntEq(result, -1);
	testIntEq(getCurrentTask()->dynamicPriority, SEM_CEILING);
	
	result = ROSA_signalSemaphoreIPCP(&s);
	testIntEq(result, 0);
	testIntEq(task->dynamicPriority, task->priority);
	
	result = ROSA_waitSemaphoreIPCP(&s, 1);
	testIntEq(result, 0);
	
	while(1);
}

void testIPCPSemaphoreWait(void)
{
	int result;
	//Create tasks and install them into the ROSA kernel
	result = ROSA_taskCreate("tst1", testIPCPSemaphoreWaitTask, 1, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	result = ROSA_taskCreate("stop", schedulerStopper, 100, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	
	result = ROSA_createSemaphoreIPCP(&s, SEM_CEILING);
	testIntEq(result, 0);
	
	//Start the ROSA kernel
	ROSA_startScheduler();
}

void testIPCPSemaphoreOutOfMemoryCondition(void)
{
	int counter=0;
	while(1)
	{
		int result = ROSA_createSemaphoreIPCP(&s, 10);
		counter++;
		switch(result)
		{
			case 0:
				continue;
			case -1:
				ee_printf("Out of memory. Created %d IPCP semaphores\n", counter);
				struct mallinfo m = mallinfo();
				ee_printf("Malloc memory used: %d free: %d bytes total: %d bytes\n", m.uordblks, m.fordblks, m.arena);				
				return;
			default:
				ee_printf("Error in IPCP memory stress test: Unknown return code: %d\n", result);
				return;
		}
	}
}

static void IPCPsemaphoreLockerOne(void)
{
	while(1)
	{
		//ee_printf("L1 %x\n", ROSA_getUserStackPointer());
		ROSA_waitSemaphoreIPCP(&s, 0);
		//delay_ms(1);
		// LockerTwo must never execute at this point - that will be a deadlock
		ROSA_waitSemaphoreIPCP(&t, 0);
		ROSA_signalSemaphoreIPCP(&t);
		ROSA_signalSemaphoreIPCP(&s);
	}
}

static void IPCPsemaphoreLockerTwo(void)
{
	while(1)
	{
		//ee_printf("L2 %x\n", ROSA_getUserStackPointer());
		ROSA_waitSemaphoreIPCP(&t, 0);
		ROSA_waitSemaphoreIPCP(&s, 0);
		ROSA_signalSemaphoreIPCP(&s);
		ROSA_signalSemaphoreIPCP(&t);
		ROSA_taskDelay(1);
	}
}

void testIPCPDeadlockCreation(void)
{
	int result;
	//Create tasks and install them into the ROSA kernel
	result = ROSA_taskCreate("tst1", IPCPsemaphoreLockerOne, 1, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	result = ROSA_taskCreate("tst2", IPCPsemaphoreLockerTwo, 2, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	
	result = ROSA_taskCreate("stop", schedulerStopper, 10, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	
	result = ROSA_createSemaphoreIPCP(&s, 2);
	testIntEq(result, 0);
	result = ROSA_createSemaphoreIPCP(&t, 2);
	testIntEq(result, 0);
	
	//Start the ROSA kernel
	ROSA_startScheduler();
	
	testIntEq(rosa_blockedQueueLength(), 0);
}

static void deadlockLockerOne(void)
{
	while(1)
	{
		ROSA_waitSemaphoreIPCP(&s, 0);
		delay_ms(1);
		ROSA_waitSemaphoreIPCP(&t, 0);
		ROSA_signalSemaphoreIPCP(&t);
		ROSA_signalSemaphoreIPCP(&s);		
	}
}

static void deadlockLockerTwo(void)
{
	ROSA_waitSemaphoreIPCP(&t, 0);
	ROSA_waitSemaphoreIPCP(&s, 0);
	ROSA_signalSemaphoreIPCP(&s);
	ROSA_signalSemaphoreIPCP(&t);		
	ROSA_taskTerminate();
}

static void lockerCreateTask(void)
{
	while(1)
	{
		ROSA_taskCreate("tskB", deadlockLockerTwo, 2, DEFAULT_STACK_SIZE);
		ROSA_taskDelay(2);
	}
}

static void testDeadlockCreationTwo(void)
{
	int result;
	result = ROSA_taskCreate("cret", lockerCreateTask, 5, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	result = ROSA_taskCreate("tskA", deadlockLockerOne, 1, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	result = ROSA_taskCreate("stop", schedulerStopper, 10, DEFAULT_STACK_SIZE);
	testIntEq(result, 0);
	
	result = ROSA_createSemaphoreIPCP(&s, 2);
	testIntEq(result, 0);
	result = ROSA_createSemaphoreIPCP(&t, 2);
	testIntEq(result, 0);
	
	ROSA_startScheduler();
	
	testBool(rosa_blockedQueueLength() < 2);
}

static TestEntry entries[] = {
	ENTRY(testSemaphoreCreation),
	ENTRY(testSemaphoreWait),
	ENTRY(testDeadlockCreation),
	ENTRY(testIPCPsemaphoreCreation),
	ENTRY(testIPCPSemaphoreWait),
	ENTRY(testSemaphoreOutOfMemoryCondition),
	ENTRY(testIPCPSemaphoreOutOfMemoryCondition),
	ENTRY(testIPCPDeadlockCreation),
	ENTRY(testDeadlockCreationTwo),
};

void testSemaphores(void)
{
	ee_printf("Testing semaphores...\n");
	performTests(entries, length(entries));
	ee_printf("Semaphore test complete\n");
}